﻿using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using System;
using Unity.Entities;

namespace Jinndev.Tile2D {

    public static class TileUtil {

        /// <summary>
        /// 指定一个中心位置，根据渲染大小计算出合适的起始位置
        /// </summary>
        /// <param name="centerIndex"></param>
        /// <param name="dimension"></param>
        /// <returns></returns>
        public static int2 GetStartIndex(int2 centerIndex, int2 dimension) {
            //dimension = math.ceil(dimension - 1);
            return centerIndex - (int2)math.floor((dimension - 1) * new float2(0.5f, 0.5f));
        }

        /// <summary>
        /// 计算一个非空Tile的pivot坐标
        /// </summary>
        public static float3 GetTilePosition(Tile tile, float z) {
            return GetTilePosition(tile.startIndex, tile.realDimension, z);
        }

        /// <summary>
        /// 根据renderConfig，计算其pivot处的坐标
        /// </summary>
        public static float3 GetTilePosition(int2 startIndex, RenderConfig renderConfig, int direction, float z) {
            return GetTilePosition(startIndex, renderConfig.GetRealDimension(direction), z);
        }

        /// <summary>
        /// 根据起始位置、轴心、大小计算Tile坐标
        /// </summary>
        /// <param name="startIndex"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public static float3 GetTilePosition(int2 startIndex, int2 realDimension, float z) {
            float2 pos = TileConst.TileSize * startIndex; // Left bottom corner
            //pos += renderSize * (0.5f - anchor) + (anchor - 0.5f) * TileConst.TileSize;
            pos += (realDimension - new int2(1, 1)) * new float2(0.5f);
            return new float3(pos.x, pos.y, z);
        }

        /// <summary>
        /// 得到index处的坐标
        /// </summary>
        public static float3 GetPosition(int2 index, float z) {
            float2 pos = GetPosition(index);
            return new float3(pos.x, pos.y, z);
        }

        /// <summary>
        /// 得到index处的坐标
        /// </summary>
        public static float2 GetPosition(int2 index) {
            return TileConst.TileSize * index;
        }

        /// <summary>
        /// 得到position处的index
        /// </summary>
        public static int2 GetIndex(float2 position) {
            return (int2)math.floor((position / TileConst.TileSize + TileConst.TilePivot));
        }

        public static int2 GetIndex(float3 position) {
            return GetIndex(position.xy);
        }

        /// <summary>
        /// 在起始index，根据配置的大小和旋转，设置N个Tile<br/>
        /// 此方法不放在TileLayer中是因为它也会在其他地方单独调用，比如预先处理存档
        /// </summary>
        /// <param name="tiles"></param>
        /// <param name="startIndex"></param>
        /// <param name="renderConfig"></param>
        /// <param name="direction"></param>
        /// <param name="forceOverride">不判断是否可放置，强制覆盖已有Tile</param>
        /// <returns>返回中心Tile（而非填充Tile）</returns>
        public static Tile SetTile(Tile[,] tiles, int2 startIndex, RenderConfig renderConfig, int direction, bool forceOverride = false) {
            Tile mainTile = Tile.Empty;
            if (renderConfig == null) {
                // 必须是带有RenderConfig数据的配置
                Debug.LogError("Invalid config");
                return mainTile;
            }
            
            int2 realDimension = renderConfig.GetRealDimension(direction);

            if (!IsRangeAvaliable(tiles, startIndex, realDimension)) {
                Debug.LogError($"Tile out of range, '{renderConfig.Sid}', start {startIndex}, dimension {realDimension}");
                return mainTile;
            }

            // 检查范围内是否被其他物体占用
            List<Tile> existTiles = GetMainTile(tiles, startIndex, realDimension);
            if(existTiles.Count > 0) {
                if (forceOverride) {
                    foreach(Tile existTile in existTiles) {
                        existTile.Foreach((x, y) => {
                            tiles[x, y] = Tile.Empty;
                        });
                        Debug.Log($"Override tiles, start: {existTile.startIndex}");
                    }
                }
                else {
                    Debug.LogError($"Tiles aren't all empty, start: {startIndex}");
                    return mainTile;
                }
            }

            // 根据当前物体的大小和旋转，从起始index创建多个Tile
            Foreach(startIndex, realDimension, (x, y) => {
                if (x == startIndex.x && y == startIndex.y) {
                    tiles[x, y] = Tile.CreateMain(Guid.NewGuid(), new int2(x, y), direction, renderConfig);
                    mainTile = tiles[x, y];
                }
                else {
                    tiles[x, y] = Tile.CreatePadding(new int2(x, y), direction, renderConfig, startIndex);
                }
            });
            return mainTile;
        }

        /// <summary>
        /// 根据tile的实际大小，删除所有Tile数据，传入的index可以是实体Tile，也可以是填充Tile
        /// </summary>
        /// <param name="tiles"></param>
        /// <param name="index"></param>
        public static void RemoveTile(Tile[,] tiles, int2 index) {
            Tile tile = tiles[index.x, index.y];
            if (tile.IsEmpty()) {
                Debug.LogError($"Removing empty tile: {index}");
                return;
            }

            if (tile.padding) {
                // 当前是填充tile，需要找到起始实体tile
                tile = tiles[tile.startIndex.x, tile.startIndex.y];
                if (tile.IsEmpty()) {
                    Debug.LogError($"Start tile is empty : {index}");
                    return;
                }
            }

            // 根据大小和旋转，全部置为Empty
            for (int y = 0; y < tile.realDimension.y; y++) {
                for (int x = 0; x < tile.realDimension.x; x++) {
                    tiles[tile.index.x + x, tile.index.y + y] = Tile.Empty;
                }
            }
        }

        public static bool IsInRange(Tile[,] tiles, int x, int y) {
            return x >= 0 && y >= 0 && x < tiles.GetLength(0) && y < tiles.GetLength(1);
        }

        public static bool IsInRange(Tile[,] tiles, int2 index) {
            return IsInRange(tiles, index.x, index.y);
        }

        /// <summary>
        /// 遍历Tile所在维度范围
        /// </summary>
        public static void Foreach(this Tile tile, Action<int, int> action) {
            Foreach(tile.startIndex, tile.realDimension, action);
        }

        /// <summary>
        /// 遍历Tile所在维度范围
        /// </summary>
        public static void Foreach(this Tile tile, Action<int2> action) {
            Foreach(tile.startIndex, tile.realDimension, action);
        }

        /// <summary>
        /// 遍历维度范围
        /// </summary>
        public static void Foreach(int2 startIndex, int2 dimension, Action<int2> action) {
            for (int y = startIndex.y; y < startIndex.y + dimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + dimension.x; x++) {
                    action.Invoke(new int2(x, y));
                }
            }
        }

        /// <summary>
        /// 遍历维度范围
        /// </summary>
        public static void Foreach(int2 startIndex, int2 realDimension, Action<int, int> action) {
            for (int y = startIndex.y; y < startIndex.y + realDimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + realDimension.x; x++) {
                    action.Invoke(x, y);
                }
            }
        }

        /// <summary>
        /// 从(0, 0)到(x, y)遍历dimension
        /// </summary>
        public static void Foreach(this int2 dimension, Action<int, int> action) {
            for (int y = 0; y < dimension.y; y++) {
                for (int x = 0; x < dimension.x; x++) {
                    action.Invoke(x, y);
                }
            }
        }

        /// <summary>
        /// 从(0, 0)到(x, y)遍历dimension
        /// </summary>
        public static void Foreach(this int2 dimension, Action<int2> action) {
            for (int y = 0; y < dimension.y; y++) {
                for (int x = 0; x < dimension.x; x++) {
                    action.Invoke(new int2(x, y));
                }
            }
        }

        /// <summary>
        /// 从(0, 0)到(x, y)遍历dimension
        /// </summary>
        public static void Foreach(this Int2 dimension, Action<int, int> action) {
            for (int y = 0; y < dimension.y; y++) {
                for (int x = 0; x < dimension.x; x++) {
                    action.Invoke(x, y);
                }
            }
        }

        /// <summary>
        /// 从(0, 0)到(x, y)遍历dimension
        /// </summary>
        public static void Foreach(this Int2 dimension, Action<int2> action) {
            for (int y = 0; y < dimension.y; y++) {
                for (int x = 0; x < dimension.x; x++) {
                    action.Invoke(new int2(x, y));
                }
            }
        }

        public static bool IsRangeAvaliable(Tile[,] tiles, int2 startIndex, int2 realDimension) {
            return startIndex.x >= 0 && (startIndex.x + realDimension.x) <= tiles.GetLength(0)
                && startIndex.y >= 0 && (startIndex.y + realDimension.y) <= tiles.GetLength(1);
        }

        /// <summary>
        /// 范围内是否全是空白
        /// </summary>
        /// <param name="tiles"></param>
        /// <param name="startIndex"></param>
        /// <param name="realDimension"></param>
        /// <returns></returns>
        public static bool IsAllEmpty(Tile[,] tiles, int2 startIndex, int2 realDimension) {
            for (int y = startIndex.y; y < startIndex.y + realDimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + realDimension.x; x++) {
                    if(!tiles[x, y].IsEmpty()) {
                        return false;
                    }
                }
            }
            return true;
        }

        public static bool IsAllEmpty(Tile[,] tiles, int2 startIndex, RenderConfig renderConfig, int direction) {
            int2 realDimension = renderConfig.GetRealDimension(direction);
            if (!IsRangeAvaliable(tiles, startIndex, realDimension)) {
                return false;
            }
            return IsAllEmpty(tiles, startIndex, realDimension);
        }

        /// <summary>
        /// 得到指定范围内的所有MainTile
        /// </summary>
        /// <param name="tiles"></param>
        /// <param name="startIndex"></param>
        /// <param name="realDimension"></param>
        /// <returns></returns>
        public static List<Tile> GetMainTile(Tile[,] tiles, int2 startIndex, int2 realDimension) {
            List<Tile> result = new List<Tile>();
            for (int y = startIndex.y; y < startIndex.y + realDimension.y; y++) {
                for (int x = startIndex.x; x < startIndex.x + realDimension.x; x++) {
                    Tile tile = tiles[x, y];
                    if (tile.IsMain()) {
                        result.Add(tile);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 得到序列帧指定帧数的TilingOffset
        /// </summary>
        /// <param name="sequence"></param>
        /// <param name="atlasDimension"></param>
        /// <param name="frame"></param>
        /// <param name="atlasTilingOffset"></param>
        /// <returns></returns>
        public static float4 GetSequenceTilingOffset(Sequence sequence, int2 atlasDimension, SequenceDefine.Anchor anchor, SequenceDefine.Direction direction, int frame) {
            float2 tiling = sequence.dimension / new float2(atlasDimension);
            float2 offset = sequence.start / new float2(atlasDimension);
            switch (direction) {
                case SequenceDefine.Direction.Right:
                    offset += new int2(frame, 0) * tiling;
                    if (offset.x >= 1) {
                        float n = math.floor(offset.x) * tiling.y;
                        offset.x = offset.x % 1;
                        if (anchor == SequenceDefine.Anchor.LowerLeft) {
                            offset.y += n;
                        }
                        else if (anchor == SequenceDefine.Anchor.UpperLeft) {
                            offset.y -= n;
                        }
                    }
                    break;
                case SequenceDefine.Direction.Left:
                    offset += new int2(-frame, 0) * tiling;
                    if(offset.x < 0) {
                        float n = math.floor(-offset.x) * tiling.y;
                        offset.x = offset.x % 1 + 1;
                        if (anchor == SequenceDefine.Anchor.LowerLeft) {
                            offset.y += n;
                        }
                        else if (anchor == SequenceDefine.Anchor.UpperLeft) {
                            offset.y -= n;
                        }
                    }
                    break;
                case SequenceDefine.Direction.Up:
                    offset += new int2(0, frame) * tiling;
                    if (offset.y >= 1) {
                        float n = math.floor(offset.y) * tiling.x;
                        offset.y = offset.y % 1;
                        offset.x += n;
                    }
                    break;
                case SequenceDefine.Direction.Down:
                    offset += new int2(0, -frame) * tiling;
                    if (offset.y < 0) {
                        float n = math.floor(-offset.y) * tiling.x;
                        offset.y = offset.y % 1 + 1;
                        offset.x += n;
                    }
                    break;
            }

            return new float4(tiling, offset);
        }

        /// <summary>
        /// 得到图集中指定元素的TilingOffset
        /// </summary>
        /// <param name="atlasDimension">图集中元素长宽数量，单位(1,1)</param>
        /// <param name="cellDimension">元素长款，单位(1,1)</param>
        /// <param name="cellIndex">元素位置，左下角(0,0)</param>
        /// <returns></returns>
        public static float4 GetTilingOffset(int2 atlasDimension, int2 cellDimension, int2 cellIndex) {
            float2 tiling = new float2(cellDimension) / atlasDimension;
            float2 offset = tiling * cellIndex;
            return new float4(tiling, offset);
        }

        public static float4 GetTilingOffset(int2 atlasDimension, int2 cellDimension, int cellX, int cellY) {
            return GetTilingOffset(atlasDimension, cellDimension, new int2(cellX, cellY));
        }

        /// <summary>
        /// 得到邻居索引
        /// </summary>
        /// <param name="index">当前索引</param>
        /// <param name="nDirection">邻居方向</param>
        /// <returns></returns>
        public static int2 GetNeighbourIndex(int2 index, int nDirection) {
            if (nDirection == Direction.UP) {
                return index + new int2(0, 1);
            }
            else if (nDirection == Direction.DOWN) {
                return index + new int2(0, -1);
            }
            else if (nDirection == Direction.LEFT) {
                return index + new int2(-1, 0);
            }
            else if (nDirection == Direction.RIGHT) {
                return index + new int2(1, 0);
            }
            return index;
        }

        /// <summary>
        /// 得到前面邻居的索引
        /// </summary>
        public static int2 GetFrontNeighbourIndex(Tile tile) {
            int2 index = tile.startIndex;
            if (tile.direction == Direction.UP) {
                index.y += tile.realDimension.y;
            }
            else if (tile.direction == Direction.RIGHT) {
                index.x += tile.realDimension.x;
            }
            else if (tile.direction == Direction.DOWN) {
                index.y -= 1;
            }
            else if (tile.direction == Direction.LEFT) {
                index.x -= 1;
            }
            return index;
        }

        /// <summary>
        /// 得到后面邻居的索引
        /// </summary>
        public static int2 GetBehindNeighbourIndex(Tile tile) {
            int2 index = tile.startIndex;
            if (tile.direction == Direction.UP) {
                index.y -= 1;
            }
            else if (tile.direction == Direction.RIGHT) {
                index.x -= 1;
            }
            else if (tile.direction == Direction.DOWN) {
                index.y += tile.realDimension.y;
            }
            else if (tile.direction == Direction.LEFT) {
                index.x += tile.realDimension.x;
            }
            return index;
        }

        /// <summary>
        /// tile是否带有指定组件
        /// </summary>
        public static bool HasComponent<T>(EntityManager entityManager, TileLayer layer, Tile tile) {
            if (layer.GetEntity(tile, out Entity entity)) {
                return entityManager.HasComponent<T>(entity);
            }
            return false;
        }

        /// <summary>
        /// 找到自身指向的邻居
        /// </summary>
        public static bool FindPointToNeighbour(Tile mainTile, IEnumerable<Tile> nTiles, out Tile nTile) {
            foreach (Tile tile in nTiles) {
                if (TileUtil.IsPointToNeighbour(mainTile, tile)) {
                    nTile = tile;
                    return true;
                }
            }
            nTile = Tile.Empty;
            return false;
        }

        /// <summary>
        /// 找到身后的邻居
        /// </summary>
        /// <returns></returns>
        public static bool FindBehindNeighbour(Tile mainTile, IEnumerable<Tile> nTiles, out Tile nTile) {
            foreach (Tile tile in nTiles) {
                if (TileUtil.IsBehindToNeighbour(tile, mainTile)) {
                    nTile = tile;
                    return true;
                }
            }
            nTile = Tile.Empty;
            return false;
        }

        /// <summary>
        /// tile是否指向相邻的nTile
        /// </summary>
        public static bool IsPointToNeighbour(Tile tile, Tile nTile) {
            int2 index = GetFrontNeighbourIndex(tile);
            return nTile.IsInRange(index);
        }

        /// <summary>
        /// tile是否在相邻的nTile身后
        /// </summary>
        /// <param name="tile"></param>
        /// <param name="nTile"></param>
        /// <returns></returns>
        public static bool IsBehindToNeighbour(Tile tile, Tile nTile) {
            int2 index = GetBehindNeighbourIndex(nTile);
            return tile.IsInRange(index);
        }
        
        public static Tile CreateTile(Tile[,] tiles, Guid guid, int2 startIndex, RenderConfig renderConfig, int direction) {
            int2 dimension = renderConfig.GetRealDimension(direction);
            Tile mainTile = Tile.Empty;

            Foreach(startIndex, dimension, (x, y) => {
                int2 index = new int2(x, y);
                if (index.Equals(startIndex)) {
                    mainTile = Tile.CreateMain(guid, index, direction, renderConfig);
                    tiles[x, y] = mainTile;
                }
                else {
                    tiles[x, y] = Tile.CreatePadding(index, direction, renderConfig, startIndex);
                }
            });
            return mainTile;
        }

    }

}
